\documentclass[a4paper,12pt,russian]{report}
\usepackage[T2A]{fontenc}
\usepackage[utf8]{inputenc} % любая желаемая кодировка
\usepackage[pdftex,unicode]{hyperref}
\usepackage{array}
\usepackage[russian]{babel} 
\usepackage{cmap}
\usepackage{listings}
\usepackage{indentfirst} % включить отступ у первого абзаца
\usepackage[top=10mm,bottom=15mm,left=15mm,right=15mm,includehead,head=12pt,headsep=0cm,includefoot,foot=10pt]{geometry}
\usepackage{graphicx}
\graphicspath{{images/}}

\usepackage{color}
\definecolor{dkgreen}{rgb}{0,0.6,0}
\definecolor{gray}{rgb}{0.5,0.5,0.5}
\definecolor{mauve}{rgb}{0.58,0,0.82}

\lstset{
  language=C, extendedchars=\true
}

\linespread{1.5} % полуторный интервал

\title{Практикум по курсу: \\{\bfПрограммно-алгоритмическое обеспечение вычислительных средств АИУС}}
\author{Кондрашов К. С.}
\date{2012}

\begin{document} % начало документа
\def\chaptername{Модуль}

\maketitle % Титульная страница
\tableofcontents

\chapter{Понятие о встраиваемых программируемых системах и системах реального времени}
\section{Примеры встраиваемых систем}
\section{Языки высокого уровня для встраиваемых систем. Язык C.}
\subsection{Структура простейшей программы}
Студенты имеют представление о структуре простейших программ из курса информатики, 
в частности, написанных на языке Pascal.

\begin{lstlisting}{language=pascal}
program Primer; 
var
   x,y,s: integer;
begin
  WriteLn('Введите через пробел два числа ');
  ReadLn(x,y);
  s := x + y;
  WriteLn('Сумма чисел равна ',s);
end.
\end{lstlisting}

Данная программа имеет {\itточку входа} (начиная с оператора begin). Аналогично программы на C имеют точку входа в виде функции main.

Определим общие правила, используемые при разработке C-программ:
\begin{itemize}
\itemв начале программы перечисляются директивы препроцессора;
\itemпрограмма состоит из совокупности функций, одна из которых должна называться main();
\itemописание функции состоит из заголовка и тела функции и называется определением функции или ее реализацией;
\itemзаголовок функции содержит тип возвращаемого функцией значения, имя функции и список параметров;
\itemимя функции узнается по круглым скобкам, которые идут за именем и могут быть пустыми;
\itemтело функции заключено в фигурные скобки и состоит из ряда инструкций.
\end{itemize}

Итак, имеем простую программу, написанную с учетом соблюдения требований стандарта ISO/ANSI C.

\begin{lstlisting}
//Директивы препроцессора
#include …

int main(void)
{
  //объявления переменных;
  //инициализация переменных;
  //вызовы функции;
  return 0;
}
\end{lstlisting}

Вслед за открывающей фигурной скобкой идут объявления переменных. Все переменные, используемые в программе, должны быть объявлены, т. е. для них должны быть указаны имена и типы значений, например:
\begin{lstlisting}
int a;
float b, c;
\end{lstlisting}

За объявлением переменных следуют инструкции, которые указывают, как и в каком порядке надо преобразовывать данные для получения требуемого ответа. Так, оператор присваивания задает начальные значения переменных. Присваивание переменной начального значения называется определением или инициализацией переменной. Наконец функция main() оканчивается инструкцией возврата return. Тело функции завершается символом \}.

\subsection{Встроенные типы данных}

Все данные, используемые в программе, можно разделить на две группы: константы и переменные. К первой группе относятся данные, не изменяющие своего значения в ходе выполнения программы, данные второй группы могут изменять свое значение.

Как константы, так и переменные могут быть различных типов, которые определяют их структуру, набор допустимых значений, правила использования и способ представления в ЭВМ. Иерархию типов данных можно представить следующей схемой.

Простые (скалярные) типы:
\begin{itemize}
\itemцелые,
\itemвещественные,
\itemсимвольные,
\itemуказатели,
\itemперечислимый тип.
\end{itemize}

Составные (структурированные) типы:
\begin{itemize}
\itemмассив,
\itemструктура,
\itemобъединение.
\end{itemize}

Переменная простого (скалярного) типа в любой момент времени хранит только одно значение. В отличие от простых переменных, переменные составного (структурированного) типа одновременно хранят несколько значений.

Целые и вещественные переменные предназначены для хранения чисел, символьные переменные – это также числовые переменные, они хранят ASCII коды символов.

Указатель – это переменная, значением которой является адрес объекта (обычно другой переменной) в памяти компьютера. Таким образом, если одна переменная содержит адрес другой переменной, то говорят, что первая переменная указывает (ссылается) на вторую.

Массив – это группа элементов одинакового типа (double, float, int и т. п.). Из объявления массива компилятор должен получить информацию о типе элементов массива и их количестве.

Структура – это совокупность элементов, объединенных под одним именем. Структура представляет собой составной объект, в который могут входить элементы различных типов. Для каждого элемента выделяется своя область памяти.

Перечислимый тип представляет собой набор целочисленных констант, используемых обычно для организаций разветвлений в программе.

Объединение – это совокупность переменных различных типов, использующих одну и ту же область памяти. Объединение подобно структуре, однако в каждый момент времени объединение может хранить значение только одного из элементов объединения. Хотя доступ к этой области памяти возможен с использованием любого из элементов, элемент для этой цели должен выбираться так, чтобы полученный результат не был бессмысленным.

Числовые типы данных языка C представлены ниже.
\begin{center}
\begin{tabular}{|c|c|c|}
\hline
Тип данных&	Размер , бит&	Диапазон значений \\
\hline
char (символьный)	& 8	& от–128 до 127\\ \hline
signed char (знаковый символьный)	& 8	& от–128 до 127\\ \hline
unsigned char (беззнаковый символьный)&	8 &	от 0 до 255\\ \hline
short int (короткое целое)	& 16 &	от–32768 до 32767\\ \hline
unsigned int (беззнаковое целое)	& 16	&от 0 до 65535 (16-битная) \\ \hline
unsigned int (беззнаковое целое)	& 16	&от 0 до 4294967295 (32-х) \\ \hline
int (целое)	& 16 & от–32768 до 32767 (16-битная) \\ \hline
int (целое)	& 32 & от –2147483648 до 2147483647 (32-х) \\ \hline
long (длинное целое)&	32	& от–2147483648 до 2147483647 \\ \hline
unsigned long (длинное целое без знака)	& 32& от 0 до 4294967295 \\ \hline
long long int (C99)	& 64	& от–(263–1) до 263–1 \\ \hline
unsigned long long int (C99)&	64	& от 0 до 264–1 \\ \hline
float (вещественное)	& 32	& от 3.4E–38 до 3.4E38 \\ \hline
double (двойное вещественное)	& 64	& от 1.7E–308 до 1.7E308 \\ \hline
long double (длинное вещественное)	& 80	& от 3.4E–4932 до 3.4E4932 \\ \hline
\_Bool (C99)	& 8	& true(1), false(0) \\ \hline
bool (C++)	& 8	& true(1), false(0) \\ \hline
\end{tabular}
\end{center}

В 32-разрядной ОС Windows типint занимает в памяти 32 бита, и диапазон допустимых значений для знакового int в этом случае от минус 2147483648 до 2147483647. Такое различие в размере памяти, выделяемой под переменную типа int, объясняется тем, что тип int – машинно-зависимый, и для него выделяется одно машинное слово, длина которого в 16-разрядных процессорах – 16 бит, в 32-разрядных – 32 бита.

Для определения данных целого типа используются ключевые слова char,int,short,long, которые определяют диапазон значений и размер области памяти, выделяемой под переменные. Так, переменная типа char занимает в памяти 1 байт, short – 2 байта, long – 4 байта. Размер переменной типа int определяется типом процессора (аппаратной платформой).

При объявлении целых типов можно использовать ключевые слова signed и unsigned, которые указывают, как интерпретируется старший бит объявляемой переменной. Если указано ключевое словоunsigned, то старший бит интерпретируется как часть числа, в противном случае старший бит интерпретируется как знаковый. В случае отсутствия ключевого слова unsigned целая переменная считается знаковой. В том случае, если спецификатор типа состоит из ключевого типа signed или unsigned и далее следует идентификатор переменной, то она будет рассматриваться как переменная типа int. Отметим, что ключевые слова signed и unsigned не обязательны.
В памяти данные хранятся в двоичном коде.

\begin{figure}[h]
  \includegraphics[width=0.75\linewidth]{int}
  \caption{Представление данных в целочисленных типах}
  \label{ris:int}
\end{figure}

Переменная типа char(signed char) занимает в памяти 1 байт, при этом старший бит хранит информацию о знаке числа: 0 соответствует положительному числу, 1 – отрицательному. Биты с 0-го по 6-й используются для записи значения числа. Запись в каждый из этих битов значения 1 соответствует наибольшему положительному числу, равному 127, при этом старший бит установлен в 0. Такое представление целых чисел называется прямым кодом. Для хранения отрицательных чисел используется представление чисел, называемое дополнительным кодом.
Получить дополнительный код отрицательного числа можно по следующему правилу:
\begin{itemize}
\item в биты, предназначенные для хранения значения числа, записывается модуль отрицательного числа в прямом коде;
\item в старший (знаковый) бит помещается 1;
\item в битах, предназначенные для хранения значения числа, формируется обратный код, т. е. 1 заменяется на 0, а 0 на 1;
\item к обратному коду числа прибавляется 1.
\end{itemize}

Рассмотрим дополнительный код для числа –1.
 
Минимальное по модулю отрицательное число представлено единицами во всех двоичных разрядах, предназначенных для хранения числа. Если в эти биты записать нули, получится наибольшее по модулю отрицательное число. Для переменной типа char это значение равно минус 128.

Переменная типа unsigned char хранит целые положительные значения, при этом все 8 бит используются для записи числа. Такое внутреннее представление числа позволяет записывать в переменную значения в диапазоне от 0 до 255.

Мы рассмотрели примеры представления целых чисел для переменной типа char, так как это самый компактный тип, но то же самое представление имеют и остальные целые типы, различия только в размере памяти, занимаемой переменной.

Однако тип char по сравнению с другими целыми типами имеет особое назначение: он используется для представления символа или объявления строковых литералов. Следует помнить, что по умолчанию тип char или signed char интерпретируется как однобайтовая целая величина со знаком и с диапазоном значений от минус 128 до 127, хотя только значения в диапазоне 0 – 127 имеют символьные эквиваленты. Для представления символов русского алфавита модификатор типа идентификатора данных имеет вид unsigned char, так как коды русских букв превышают величину 127.

Для определения данных, представляющих число с плавающей точкой, используются ключевые слова float,double,long double.

Величина типа float занимает в памяти 4 байта, из которых 1 бит отводится для знака, 8 битов для порядка и 23 бита для мантиссы. Порядок хранится в виде двоичного положительного числа, которое получается при сложении величины порядка и числа 127 ($1111111_2$). Мантисса хранится в нормализованном виде, т. е. ее значение должно быть в диапазоне от 1 до 2, а старший бит равен 1. Если нормализация мантиссы нарушается, выполняется сдвиг мантиссы до тех пор, пока старшей цифрой не окажется единица, одновременно при каждой операции сдвига мантиссы происходит изменение величины порядка. Так как старшая цифра в нормализованной мантиссе – единица, она отбрасывается. Отброшенная единица называется неявной единицей. Диапазон значений переменной с плавающей точкой приблизительно равен от 3.4E–38 до 3.4E+38 , а точность (количество значащих цифр) равна 7.

Рассмотрим, каким образом в ячейках памяти, выделенных под переменную типа float, хранится конкретное число.
Пусть float x=5.375; в двоичной системе $x_2=101.011$ или $x_2=1.01011\cdot2^{10+1111111}$, тогда с учетом того, что в нормализованной мантиссе первая цифра всегда 1, и эта единица отбрасывается, а порядок сдвигается на 127, получаем внутреннее представление числа x (начиная с 31-го бита, заканчивая 0-ым): 0 (знак) 1000 0001 (порядок) 0101 1000 0000 0000 0000 000 (мантисса).

Величина типа double занимает 8 байт в памяти, формат которой аналогичен формату float. Биты памяти распределяются следующим образом: 1 бит – знак, 11 битов – порядок и 52 бита – мантисса. Порядок сдвигается на величину 1023. С учетом опущенного старшего бита мантиссы диапазон значений равен от 1.7E–308 до 1.7E+308, а точность составляет 15 десятичных значащих цифр.


\subsection{<<Словарь>> языка C}

Множество символов, используемых в тексте программы на C, можно разделить на пять групп:
\begin{itemize}
\itemсимволы, используемые для записи ключевых слов и идентификаторов, к которым относятся прописные и строчные буквы английского алфавита и символ подчеркивание ( \_ ). Следует отметить, что одинаковые прописные и строчные буквы считаются различными символами, так как имеют различные коды (A, a – разные символы). Компиляторы языков C и C++ распознают прописные и строчные буквы;
\itemарабские цифры 0, ..., 9;
\itemразделители: , . ; : ? ' " ! | / \^ ~ * () \{\} [] <> + ? \& \# \% = \_ . $\backslash$, использующиеся, с одной стороны, для организации процесса вычислений, а с другой – для передачи компилятору определенного набора инструкций;
\itemпробельные символы (пробел, символы табуляции, перевода строки, конца файла), отделяющие друг от друга объекты, определяемые пользователем (константы, идентификаторы). Последовательность пробельных символов рассматривается компилятором как один символ (последовательность пробелов);
\item{ESC}-символы (escape-символы, escape-последовательности или управляющие последовательности), т. е. последовательность символов, начинающаяся с символа обратной наклонной черты, используемая для представления в программе кода одного символа, как печатаемого, так и непечатаемого (управляющего).
\end{itemize}
$\backslash$a – звуковой сигнал	$\backslash$b – возврат на шаг	$\backslash$f – перевод страницы
$\backslash$n – новая строка	$\backslash$r – возврат каретки	$\backslash$t – гориз. табуляция
$\backslash$v – верт. табуляция	$\backslash$ – наклонная черта	$\backslash$" – двойная кавычка
$\backslash$' – одиночная кавычка	$\backslash$ooo – 8-ричный код	$\backslash$xhh – 16-ричный код.

\subsection{Объявления переменных}
Одним из отличий языка C от ряда других языков программирования является отсутствие принципа умолчания, что приводит к необходимости явного объявления всех переменных, используемых в программе, вместе с указанием соответствующих им типов.

Объявление переменной имеет следующий формат:

{\it
[спецификатор\_класса\_памяти] спецификатор\_типа идентификатор [=инициатор].
}

Спецификатор класса памяти определяется одним из 4 ключевых слов языка C: auto, extern, register, static и указывает, во-первых, каким образом будет распределяться память под объявляемую переменную и, во-вторых, область видимости этой переменной, т. е. из каких частей программы можно к ней обратиться.

Спецификатор типа - одно или несколько ключевых слов, определяющих тип объявляемой переменной.

Инициатор задает начальное значение или список начальных значений, присваиваемых переменной при объявлении.

Примеры инициализации переменных:
\begin{lstlisting}
int i=5; float f=12.35; char ch='a';
int k=0, b=5, d=7;
\end{lstlisting}

Переменная любого типа может быть объявлена немодифицируемой, что достигается добавлением ключевого слова const к спецификатору типа. Объекты с типом const представляют собой данные, используемые только для чтения, т. е. этой переменной не может быть присвоено новое значение: например, const int a=5.

Отметим, что если после слова const отсутствует спецификатор типа, то подразумевается спецификатор типа int. Если ключевое слово const стоит перед объявлением составных типов (массив, структура, объединение), то это приводит к тому, что каждый элемент также должен являться немодифицируемым, т. е. значение ему может быть присвоено только один раз.

Ключевое слово void означает отсутствие типа.

\subsection{Выражения}
В качестве выражений в языке C рассматривается совокупность элементов данных (переменных, констант и вызовов функций), объединенных знаками операций и скобками. Каждое выражение должно иметь свое значение. Результат вычисления выражения зависит от расположения знаков операций и круглых скобок в выражении, а также от приоритета выполнения операций.

Элементы данных, используемые в выражении, называются операндами. Каждый операнд имеет тип. При вычислении выражений тип каждого операнда может быть преобразован к другому типу. Преобразования типов бывают неявными (при вычислении выражений и вызовах функций) или явными (при выполнении операций преобразования типов).    
Неявное преобразование из типа int в тип float:

\begin{lstlisting}
int a=5; float b=a;
\end{lstlisting}

Явное преобразование типа int в тип float с помощью операции преобразования типов (тип в явном виде указывается в скобках перед преобразуемой переменной):

\begin{lstlisting}
int a=5; float b=(float)a;
\end{lstlisting}

Если в качестве операнда используется числовая константа, то тип операнда устанавливается в зависимости от значения константы. Константа, представленная целым числом, может быть типаint,long,unsigned int или unsigned long в зависимости от ее значения и от формы записи. По умолчанию компилятор приписывает константе тип наименьшего размера, в ячейку которого может уместиться константа. Но это правило имеет исключение: всем вещественным константам, даже самым маленьким, приписывается тип double.

Большинство компиляторов обеспечивает стандартный синтаксис языка C, согласно которому в символьной константе может быть только один печатный символ или один управляющий код. Следуя стандартному синтаксису, символьная константа требует один байт памяти и имеет тип char.

Строковый литерал состоит из последовательности символов, заключенных в кавычки, и представляется в памяти как массив элементов типа char, инициализируемый указанной последовательностью символов. Следует помнить, что последним символом строки (строкового литерала) всегда является нулевой символ, который автоматически добавляется при хранении строки в памяти.

Строковые литералы могут быть использованы в качестве операндов в выражениях, допускающих величины типа указателей. Так как строки являются константами, их нельзя использовать в левой части операции присваивания.
В языках C и C++ выражение рассматривается как имеющее значение true, если результат вычислений не равен нулю, и false в противном случае.

\subsection{Арифметические выражения}
К арифметическим операциям относятся операции сложения (+), вычитания (–), умножения (*), деления (/) и деления по модулю (\%) – вычисление остатка от деления. В языке отсутствует операция возведения в степень.

В одном и том же выражении могут встретиться переменные и константы разных типов, в этом случае они преобразуются к старшему типу в соответствии с приоритетами типов, установленными в языке C: long double – double – float – unsigned long – long – unsigned int – int – unsigned char – char.

В этом перечислении наивысший приоритет имеет типlong double. Компилятор преобразует “меньший” тип в “больший”. Например, если в выражении операнды имеют типыlong double,double,float и т. д., то все они на время вычислений автоматически преобразуются к старшему типу. В данном случае это будет типlong double. Тип результата будет соответствовать старшему типу в выражении.

Отметим, что в соответствии с правилами выполнения вычислений в C при делении двух операндов целого типа результат также является целочисленным и формируется путем отбрасывания дробной части частного от деления: 4/7=0; 7/4=1.

Деление по модулю выполняется над операндами целого типа и результатом операции является остаток от деления: 7\%4=3; 4\%7=0.

Приоритеты арифметических операций:
\begin{itemize}
\item сложение, вычитание.
\item умножение, деление, деление по модулю;
\end{itemize}

Операции одного уровня выполняются слева направо.

Помимо стандартных арифметических операций используются две дополнительных операции: инкремент (++) и декремент (– –). Результатом выполнения инкремента будет увеличение значения операнда на 1, результат декремента – уменьшение операнда на 1. Обе операции идентичны, поэтому рассмотрим на примере только операцию инкремента (увеличения).

Операция инкремента реализуется в двух видах: ++х; x++; аналогично декремент: ––х; x––; где х – идентификатор переменной.

Строго говоря, ограничений на тип переменной нет, но реально эти операции работают с переменными целого типа.

Результатом операции является увеличение значения переменной х на 1, причем в первом случае х сначала увеличивается на 1, а затем используется в дальнейших операциях, а во втором – значение х сначала используется, а потом увеличивается на 1. Различие операций сказывается при использовании их в сложных выражениях. Например, определить значения x,t,z,y после вычисления выражений при х=1, t=1:

y=++x; z=t++;

Ответ: x=2; t=2; z=1; y=2.

Дополнительные арифметические операции используются, в основном, для организации счетчиков в циклах и имеют следующие ограничения:
\begin{itemize}
\item операции не следует использовать по отношению к переменным, которые встречаются в выражении более одного раза, т. е. не следует использовать выражение типа х*х++;
\item операции инкремента и декремента можно применять только к переменным, а не к результату, т. е. выражение (х*y)++ неверное;
\item операции не рекомендуется использовать в логических выражениях.
\end{itemize}

Данные операции имеют более высокий приоритет по сравнению с другими арифметическими операциями.

\subsection{Функция: вызов, объявление и определение}
C-программа состоит из функций, которые являются строительными элементами языка.

Каждая функция имеет заголовок и тело. Заголовок включает в себя имя функции, список аргументов, заключенный в круглые скобки, и тип возвращаемого функцией значения.

За строкой, содержащей имя функции, идет тело функции, заключенное в фигурные скобки.

В общем виде синтаксис функции выглядит следующим образом:

{\it
возвр\_тип имя\_функции \(список\_параметров\)
\{
тело функции
\}
}

Тело функции содержит последовательность операторов языка, выполняющих некоторые действия.

Возвр\_тип определяет тип значения (результата), возвращаемого функцией. Функция может возвращать значение любого типа, за исключением массивов. 

Имя\_функции представляет собой идентификатор, после имени функции обязательно следуют круглые скобки. В скобках указывается список формальных параметров. Каждый элемент списка формальныхпараметров состоит из имени переменной и ее типа. Все параметры функции должны объявляться отдельно, причем для каждого из них надо указывать и тип, и имя. При вызове функции формальные параметры заменяются значениями фактических аргументов.

Список параметров может быть и пустым. Такой пустой список можно указать в явном виде, поместив внутри скобок ключевое словоvoid, или оставить скобки пустыми:

Функции C, возвращающие значения, делают это с помощью оператора возврата return, за которым следует возвращаемое значение. В функциях типа void используется оператор return без значения или операторreturn не используется вовсе.

В современных программах на языке C каждую функцию перед использованием необходимо объявлять. Объявление функции называется прототипом функции. Например, для функции MyFunction(), прототип будет иметь следующий вид:

\begin{lstlisting}
void MyFunction(void);
\end{lstlisting}

Хотя прототипы в языке C формально не требуются, их применение очень желательно. Прототипы дают компилятору возможность тщательно выполнить проверку типов аргументов, правильность их преобразования, обнаружить несоответствия в количестве аргументов, использованных при вызове функции, и в количестве параметров функции.

\subsection{Препроцессор и его функции}
По сравнению с другими языками программирования в языке C есть принципиально новое понятие – препроцессор, позволяющий облегчить написание и отладку C-программ.
Препроцессор -- программа, используемая для обработки исходного текста программы на языке С до компиляции и выполняющая следующие действия:
\begin{itemize}
\itemпоиск и включение в программу нужных внешних файлов;
\itemизменение условий компиляции;
\itemопределение значений констант и т. д.
\end{itemize}

Препроцессор <<общается>> с программой при помощи директив. Директивы препроцессора представляют собой инструкции, записанные в тексте программы и выполняемые до ее трансляции. Директивы препроцессора позволяют изменить текст программы, например, заменить некоторые лексемы в тексте, вставить текст из другого файла, запретить трансляцию части текста и т. п. Директивы препроцессора отмечаются специальным маркером\#. Знак \# должен быть первым символом в строке, содержащей директиву. Между знаком \# и первой буквой директивы могут находиться пробелы. После директив препроцессора точка с запятой не ставится.

Директивы препроцессора могут встречаться в любом месте программы, но обычно их стараются помещать в начале для удобства восприятия текста программы. Директивы, появляющиеся в любом месте исходного файла, применимы только к тексту, идущему после этой директивы. Написание директивы препроцессора завершается нажатием клавиши Enter. Для написания директивы в две строки в месте переноса следует нажать комбинацию клавиш \textit{[$\backslash$] + [Enter]}, и директива продолжается в следующей строке.

Обработанный препроцессором текст программы уже не содержит директив препроцессора, имеет гораздо больший объем по сравнению с исходным текстом. В таком виде программа передается для дальнейшей обработки компилятору.

\subsection{Основные директивы препроцессора}
\subsubsection{Директива include}
Директива включает в текст программы содержимое указанного файла и имеет две формы:
\begin{lstlisting}
#include "filename"
#include <filename>
\end{lstlisting}

Имя файла должно соответствовать соглашениям операционной системы и может состоять только из имени файла, либо из имени файла с указанием пути к этому файлу. Способ поиска файла зависит от того, заключено ли его имя в двойные кавычки или в угловые скобки. Если имя файла задано в угловых скобках, поиск файла проводится в специальном каталоге. Обычно таким каталогом является каталог INCLUDE интегрированной среды разработки (IDE). Если имя файла указано в кавычках, то поиск файла начинается с текущего каталога, а если файл не найден, поиск продолжается в каталоге INCLUDE.

Директива include широко используется для включения в программу так называемых заголовочных файлов, содержащих прототипы библиотечных функций, и поэтому большинство программ на С начинаются с этой директивы.

Заголовочный файл (объект-заголовок) снабжает компилятор необходимой информацией о данных и функциях, которые могут использоваться в программе, имеет расширение .h (header) и представляет собой текстовый файл. Заголовочный файл может содержать определение типов, прототипы функций, объявление внешних переменных (extern), директивы препроцессора и комментарии.

Любая C-программа должна содержать хотя бы одну директиву. Так, для реализации ввода/вывода должна присутствовать директива:
\begin{lstlisting}
#include <stdio.h>
\end{lstlisting}

Файл stdio.h (standard input/output header) содержит необходимую информацию о средствах ввода/вывода информации:
\begin{itemize}
\itemопределение типа данных FILE (поток ввода/вывода идентифицируется указателем на переменную типа FILE);
\itemописание потоков stdin и stdout (ввод/вывод); по умолчанию эти файлы связаны с терминальным оборудованием (ввод с клавиатуры, вывод на дисплей);
\itemфайл вывода сообщений об ошибкахstderr;
\itemопределение макроса NULL (нулевой указатель, т. е. значение, не указывающее ни на какой объект);
\itemопределение макроса EOF (признак конца файла).
\end{itemize}

\subsubsection{Директива define}
Директива define служит для замены часто использующихся констант, ключевых слов, операторов или выражений некоторыми идентификаторами - макросами. Идентификаторы, заменяющие текстовые или числовые константы, называют именованными или символическими константами. Идентификаторы, заменяющие фрагменты программ, называют макроопределениями, причем макроопределения могут иметь аргументы.

Директива define имеет две синтаксические формы:
\begin{lstlisting}
#define id text
#define id(args) text
\end{lstlisting}
Эта директива заменяет все последующие вхождения идентификатора текстом. Такой процесс называется макроподстановкой. Текст может представлять собой любой фрагмент программы на C, а может и отсутствовать вовсе. В последнем случае все экземпляры идентификатора удаляются из программы.
Идентификатор (макрос) в директиве define принято записывать прописными буквами:
\begin{lstlisting}
#define WIDTH 80
#define LENGTH (WIDTH+10)
\end{lstlisting}

Эти директивы изменят в тексте программы каждое слово WIDTH на число 80, а каждое слово LENGTH на выражение (80+10) вместе с окружающими его скобками.

Скобки, содержащиеся в макроопределении, позволяют избежать недоразумений, связанных с порядком выполнения операций. Так, при отсутствии скобок выражение t=LENGTH*7 будет преобразовано в выражение t=80+10*7, а не в выражение t=(80+10)*7, как это получается при наличии скобок, и в результате вычислений получится число 150, а не 630.

Во второй синтаксической форме в директиве define имеется список формальных параметров, который может содержать один или несколько идентификаторов, разделенных запятыми. Формальные параметры в тексте макроопределения отмечают позиции, на которые должны быть подставлены фактические аргументы макровызова. Каждый формальный параметр может появиться в тексте макроопределения несколько раз. При макровызове вслед за идентификатором записывается список фактических аргументов, количество которых должно совпадать с количеством формальных параметров.

Пример макроопределения:
\begin{lstlisting}
#define SQUARE(X) (X)*(X)
\end{lstlisting}

Отметим, что скобки необходимы (как и в предыдущем примере) для обеспечения правильного порядка действий.

Теперь при появлении в программе выражения Z=SQUARE(2); переменная Z получит значение, равное 4.

Следует иметь в виду, что препроцессор не выполняет проверки синтаксической правильности текста подстановки, поэтому использование конструкции
\begin{lstlisting}
#define PI = 3.1415
\end{lstlisting}

приведет к тому, что вместо идентификатора PI везде будет подставляться текст "= 3.1415", что явно не соответствует желаемому. Ошибка будет обнаружена только на этапе компиляции.

\subsubsection{Директива undef}
Директива undef используется для отмены действия директивы define, синтаксис которой
\begin{lstlisting}
#undef id
\end{lstlisting}

Директива отменяет действие текущего определения define для указанного идентификатора. Не является ошибкой использование директивы undef для идентификатора, который не был определен директивой define, например:
\begin{lstlisting}
#undef WIDTH
#undef MAX.
\end{lstlisting}

\subsection{Управляющие операторы}
Оператором называется конструкция языка программирования, обозначающая какие-либо действия в программе. Среди большого количества операторов можно выделить операторы, управляющие ходом вычислений. Такие операторы называют управляющими операторами (инструкциями). Другая группа операторов выполняет действия над данными, например, +, \%, ++ и т. д. Такие операторы в языке C традиционно называют операциями, а в C++ операторами. Поскольку в первых главах книги рассматривается язык C, то и терминология будет соответствовать этому языку.

Операторы, управляющие последовательностью выполнения действий в программах на языке C, называются просто операторами.

В языке C можно выделить следующие группы операторов:
\begin{itemize}
\itemусловные;
\itemцикла;
\itemбезусловного перехода;
\itemоператоры-выражения;
\itemблоки (составные операторы).
\end{itemize}

К условным относятся операторы if и switch, к операторам цикла – for, while и do-while, к операторам безусловного перехода - break, continue, goto и return.

Операторы-выражения – это операторы, состоящие из операндов (переменных, констант, функций), знаков операций и круглых скобок, обозначающих порядок действий над операндами.

Блок или составной оператор представляет собой фрагмент текста программы, заключенный в фигурные скобки \{\}.
\subsubsection{Логические выражения}
При выполнении многих операторов вначале анализируются данные (вычисляется некоторое логическое выражение), и в зависимости от полученного результата выбирается та или иная ветвь вычислительного процесса.

Логическое выражение (ЛВ) – это выражение, принимающее одно из двух значений: истина или ложь.

В отличие от других языков программирования в C (C89) нет специального типа для логических переменных и констант. Однако результат у ЛВ есть всегда. В языке C, если значение ЛВ равно нулю, то оно ложно, любое ненулевое ЛВ истинно.

Простейшие ЛВ – любые выражения (в частности, переменная, арифметическое выражение).

Отношения (следующий уровень ЛВ) позволяют сравнивать данные.

Отношение имеет структуру: \textit{V1 OP V2;} где V1, V2 – выражения, OP – знак операции отношения.

Операции <, <=, >, >= являются операциями одного уровня и выполняются в порядке их написания слева/направо; операции == и != имеют более низкий приоритет.
Следующий уровень ЛВ – логические выражения с использованием логических операций И(\&\&), ИЛИ(||), НЕ (!). В круглых скобках представлены обозначения этих операций в C.

\subsubsection{Формы оператора if}
В общем виде синтаксис оператора if следующий:
\begin{lstlisting}
if (LE) OP1;
else OP2.
\end{lstlisting}
При истинном значении ЛВ выполняется оператор ОР1, в противном случае – ОР2, иногда используется и иная форма условного оператора

\begin{lstlisting}
if (LE) OP;
\end{lstlisting}
если ЛВ истинно - выполняется OP, в противном случае OP пропускается.

Если при ЛВ, имеющем значение <<истина>>, необходимо выполнение нескольких операторов, то их надо заключить в фигурные скобки \{\}.

\subsection{Оператор выбора switch}
Оператор выбора switch (или переключатель) предназначен для выбора ветви вычислительного процесса исходя из значения управляющего выражения. Использование данного оператора целесообразно при сложном условном ветвлении.

Структура оператора следующая:
\begin{lstlisting}
switch (exp)
{
case const_exp1 : op
break;
case const_exp2 : op
break;
default : op
}
\end{lstlisting}

Значение выражения оператора switch должно быть целочисленным. Это означает, что в выражении можно использовать переменные только целого или символьного типа. Результат вычисления выражения по очереди сравнивается с каждым из константных выражений. Если в какой-либо строке находится совпадение, управление передается на соответствующую меткуcase, и выполняется связанная с ней группа операторов. Выполнение продолжается до конца тела оператора switch или пока не встретится оператор break, который передает управление из тела switch оператору, следующему за закрывающей данную конструкцию фигурной скобкой.

Применение оператора break в контексте блока switch является обычным. Без него после выполнения варианта case, который соответствует значению управляющего выражения, оператор switch продолжит свою работу, при этом будут выполнены все последующие варианты case и ветка default.

Оператор, связанный с default, выполняется, если выражение не совпало ни с одним из константных выражений в case. Оператор default не обязательно располагается в конце конструкции. Кроме того, он и сам не обязателен. В этом случае при отсутствии совпадений не выполняется ни один оператор. Не допускается совпадение константных выражений.

\subsubsection{Оператор while}
Ключевое слово while позволяет выполнять оператор или группу операторов до тех пор, пока условие не перестанет быть истинным.

Синтаксис: while(ЛВ) ОP;
где ЛВ – логическое выражение, ОP – тело цикла (простой или составной оператор).

Итак:
если ЛВ – истинно, выполняется ОР, затем снова проверяется ЛВ и так далее;
если ЛВ – ложно, ОР пропускается, и управление передается на оператор, следующий за телом цикла.

Поскольку проверка ЛВ выполняется перед телом цикла, цикл while называют циклом с предусловием.

Если ЛВ изначально ложно, тело цикла while не выполнится ни разу.

\subsubsection{Оператор for}
Оператор цикла for позволяет выполнять оператор или группу операторов заранее заданное количество раз.

Общая форма оператора
for (V1; ЛВ; V2) ОP;

где V1 – выражение, в котором производится присваивание переменной, называемой параметром цикла, начального значения (инициализация); ЛВ – условие, определяющее, следует ли в очередной раз выполнять оператор (тело цикла); V2 – выражение, в котором производится изменение переменной цикла (приращение); ОР – оператор или группа операторов.

Принцип работы:
\begin{itemize}
\itemвычисляется V1;
\itemвычисляется ЛВ;
\itemесли ЛВ истинно, выполняется ОР, затем вычисляется V2;
\itemпроверка ЛВ > ОP > V2 и т. д.
\end{itemize}
Когда ЛВ становится ложным, осуществляется переход к оператору, следующему за ОP. Если ОP – составной оператор, то его надо заключить в \{\}.
Если ЛВ сразу ложно, то ни ОР, ни V2 не выполняются ни разу.

\subsubsection{Оператор do-while}
В цикле do-while проверка условия проводится после выполнения тела цикла:
do ОP while(ЛВ)

Действие
\begin{itemize}
\itemвыполняется ОР;
\itemесли ЛВ истинно, повторяется выполнение ОР, в противном случае осуществляется переход к оператору, следующему за while.
\end{itemize}

Если в цикле должно выполняться несколько операторов, они заключаются в \{\}. Данный цикл носит название цикла с постусловием, т. е. при любом ЛВ тело цикла выполняется хотя бы один раз.

\subsubsection{Оператор break}
Оператор break применяется в двух случаях. Во-первых, в операторе switch с его помощью прерывается выполнение последовательности case. Во-вторых, оператор break используется для немедленного прекращения выполнения цикла без проверки его условия и передачи управления оператору, следующему после оператора цикла.

\subsubsection{Оператор continue}
Можно сказать, что оператор continue немного похож на оператор break. Оператор break вызывает прерывание цикла, а continue – прерывание текущей итерации цикла и переход к следующей итерации.

\subsection{Оператор return}
Завершает выполнение функции, в которой он задан, и возвращает управление в вызывающую функцию. Управление передается в вызывающую функцию в точку, непосредственно следующую за вызовом.

Синтаксис: return (выражение);

Значение вычисленного в операторе return выражения возвращается в вызывающую функцию в качестве результата вызываемой функции. Если выражение опущено, то возвращаемое функцией значение не определено (функции типа void).

Если оператор return в вызываемой функции отсутствует, управление автоматически передается в вызывающую функцию после выполнения последнего оператора функции. Возвращаемое функцией значение в этом случае не определено.

Итак, оператор return используется в 2-х случаях:
\begin{itemize}
\itemесли надо немедленно выйти из функции;
\itemесли функция должна возвращать значение.
\end{itemize}

\subsection{Указатели}
Указатель -- это переменная, значением которой является адрес некоторого объекта (обычно другой переменной) в памяти компьютера. Подобно тому, как переменная типа char имеет в качестве значения символ, а переменная типа int – целочисленное значение, переменная типа указателя имеет в качестве значения адрес ячейки оперативной памяти. Допустимые значения для переменной-указателя – множество адресов оперативной памяти компьютера.

Указатель является одной из наиболее важных концепций языка C.

Итак, указатель – это новый тип данных. Для него определены понятия константы, переменной, массива. Как и любую переменную, указатель необходимо объявить. Объявление указателя состоит из имени базового типа, символа * (звездочка) и имени переменной.

Общая форма объявления указателя:
тип *имя;
Тип указателя определяет тип объекта, на который указатель будет ссылаться, например,
int *p1;

Фактически указатель любого типа может ссылаться на любое место в памяти, но выполняемые над указателем операции существенно зависят от его типа. Так, если объявлен указатель типа int *, компилятор предполагает, что любой адрес, на который он ссылается, содержит переменную типа int, хотя это может быть и не так. Следовательно, объявляя указатель, необходимо убедиться в том, что его тип совместим с типом объекта, на который он будет ссылаться.

\subsubsection{Операция получения адреса}
Понятие указателя тесно связано с понятием адреса объекта. В C есть специальная операция, позволяющая получить адрес любой переменной:
\&p – получение адреса, где p – идентификатор переменной. Результатом операции является адрес переменной p.

Понятие переменной типа указатель также связано с операцией косвенной адресации *, называемой еще операцией разыменования, которая имеет структуру: *р – разыменование, где р – идентификатор переменной-указателя. Эта запись означает, что в ячейку с адресом, записанным в переменную р, помещено значение некоторой величины.

Операторы ptr=\&a; и val=*ptr; равнозначны оператору val=a;
Например,
\begin{lstlisting}
n=32;
p=&n; 
v=*p;
\end{lstlisting}

В результате выполнения этих действий в переменную v будет помещено число 32.

\subsubsection{Операции над указателями}
Над указателями определено 5 основных операций.
\begin{itemize}
\itemОпределение адреса указателя: \&p, где p – указатель (\&p – адрес ячейки, в которой находится указатель).
\itemПрисваивание. Указателю можно присвоить адрес переменной p=\&q, где p – указатель, q – идентификатор переменной.
\itemОпределение значения, на которое ссылается указатель: *p (операция косвенной адресации).
\itemУвеличение (уменьшение) указателя. Увеличение выполняется как с помощью операции сложения (+), так и с помощью операции инкремента (++). Уменьшение – с помощью операции вычитания (–) либо декремента (––).
\end{itemize}

Например, пусть p1 – указатель, тогда р1++ перемещает указатель на:
\begin{itemize}
\item1 байт, если *p1 имеет тип char;
\item4 байта, если *p1 имеет тип int (в 32 разрядной операционной системе) или 2 байта (в 16 разрядной операционной системе);
\item4 байта, если *p1 имеет тип float.
\end{itemize}

Пусть р1 и р2 – указатели одного и того же типа. Можно определить разность р1 и р2, чтобы найти, на каком расстоянии друг от друга находятся элементы массива.

Операции адресной арифметики подчиняются следующим правилам. После увеличения значения переменной-указателя на 1 данный указатель будет ссылаться на следующий объект своего базового типа. После уменьшения – на предыдущий объект. Для всех указателей адрес увеличивается или уменьшается на величину, равную размеру объекта того типа, на который они указывают. Поэтому указатель всегда ссылается на объект с типом, тождественным базовому типу указателя.

Применительно к указателям на объект типа char операции адресной арифметики выполняются как обычные арифметические операции, потому что длина объекта char всегда равна 1.

Операции адресной арифметики не ограничены увеличением (инкрементом) и уменьшением (декрементом). К указателям, например, можно добавлять или вычитать из них целые числа.

При операции вычитания двух указателей можно определить количество объектов, расположенных между адресами, на которые указывают эти два указателя. При этом необходимо, чтобы указатели имели один и тот же тип. Кроме того, стандартом C допускается сравнение двух указателей. Как правило, сравнение указателей может оказаться полезным только тогда, когда два указателя ссылаются на общий объект, например, на массив.

Все остальные операции над указателями запрещены.

\subsection{Массивы}
Несколько переменных одного типа можно объединить под одним именем. Такая переменная будет представлять собой массив. Массив -- это тип данных, представляющий собой ограниченный набор упорядоченных элементов одного и того же типа, имеющих одно и то же имя.

Элементом массива является переменная. Количество элементов массива определено заранее при объявлении массива. Все элементы упорядочены -– каждому присвоен порядковый номер, который называется индексом. Доступ к конкретному элементу массива осуществляется с помощью индекса. В языке C все массивы располагаются в отдельной непрерывной области памяти. Первый элемент массива имеет наименьший адрес, а последний – наибольший.

Элементы массива могут быть как простыми переменными, так и составными. Элемент массива может иметь несколько индексов. Количество индексов переменной определяет размерность массива. Размерность массивов в языке C не ограничена, но чаще используются одномерные и двумерные массивы. Начальное значение индекса элемента массива для каждого измерения в C – нуль.

\subsubsection{Одномерные массивы}
Как и другие переменные, массив должен быть объявлен. Существует несколько способов объявления массива.

Общая форма объявления одномерного массива имеет следующий вид:
\textit{<класс> тип имя [размер]}
где класс – необязательный элемент, определяющий класс памяти (extern, static, register);
тип – базовый тип элемента массива;
имя – идентификатор массива;
размер – количество элементов в массиве.

Доступ к элементу массива осуществляется с помощью имени массива и индекса. Индекс элемента массива помещается в квадратных скобках после имени. Нижнее значение индекса всегда нуль.
Таким образом, элементами массива, состоящего из N элементов, являются переменные с индексами
a[0],a[1],$\cdots$, a[N–1].
В качестве N в описании должна стоять целая положительная константа.

Пример программы
\begin{lstlisting}
# include <stdio.h>
# define N 15
int main()
{
  float x[N],s;
  int i;
  for (i=0,s=0;i<N;++i)
  {
    printf("\n Input x[%d]",i);
    scanf("%f",&x[i]);
    s+=x[i];
  }
  printf("\n S=%f",s);
  return 0;
}
\end{lstlisting}

Объем памяти, необходимый для хранения массива, определяется типом элемента массива и количеством элементов, входящих в массив.

Для одномерного массива требуемый объем памяти вычисляется следующим образом:

\textit{Объем памяти в байтах = sizeof(базовый тип элемента) длина массива.}

Во время выполнения программы не проверяется ни соблюдение границ массива, ни его содержимое. Задача проверки и соответственно корректности выполнения программы возложена на программиста.

В языке C массивы при объявлении можно инициализировать. Общая форма инициализации массива аналогична инициализации переменной:

\textit{<класс> тип имя [размер] = \{список значений\};}

Список значений представляет собой список констант, перечисленных в фигурных скобках через запятую. Типы констант должны быть совместимы с типом массива. Первая константа присваивается первому элементу массива, имеющему индекс нуль, вторая – второму и так далее. После закрывающей фигурной скобки точка с запятой обязательна:
\begin{lstlisting}
static float x[5]={7.5,0,–3.2,0,4};
\end{lstlisting}

В этом случае будет создан массив на 5 элементов. Если в списке инициализации задать количество элементов меньше, чем задано в объявлении массива, то остальные элементы принимаются равными нулю. Если в списке инициализации задать количество элементов больше, чем задано в объявлении массива, это вызовет ошибку при компиляции программы.

Количество элементов в предыдущей записи может быть опущено.

\subsubsection{Двумерные массивы}
Стандартом C определены многомерные массивы. Простейшая форма многомерного массива – двумерный массив, т. е. массив одномерных массивов.

Описание двумерного массива выглядит следующим образом:

\textit{<класс> тип имя [N1] [N2];}
где N1,N2 – количество строк и столбцов.

Массив в памяти располагается по строкам.

Как и в случае одномерных массивов, возможна инициализация двумерных массивов:
\begin{lstlisting}
int a[2][3]=
{
  {1,–2,7},
  {2,–3,9}
};
\end{lstlisting}

Пример программы
\begin{lstlisting}
# include <stdio.h>
int main()
{
  int a[2][3]=
  {
    {6,–1},
    {–3,2}
  };
  int i,j;
  for (i=0;i<2;++i)
    for (j=0;j<3;++j)
      printf("\n a[%d,%d]=%d",i,j,a[i][j]);
}
\end{lstlisting}
Ответ: a[0,0]=6, a[0,1]=–1, a[0,2] = 0, a[1,0]=–3, a[1,1]=2, a[1,2]=0.

\subsubsection{Массивы и указатели}
В языке C массивы и указатели тесно связаны друг с другом. Например, когда объявляется массив в виде int a[25], то при этом не только выделяется память для 25 элементов массива, но и формируется указатель с именем a, значение которого равно адресу первого по счету (нулевого) элемента массива. Доступ к элементам массива может осуществляться через указатель с именем a. С точки зрения синтаксиса языка указатель a является константой, значение которой можно использовать в выражениях, но изменить это значение нельзя.

Поскольку имя массива является указателем-константой, допустимо, например, такое присваивание:
\begin{lstlisting}
int a[25];
int *ptr;
ptr=a;
\end{lstlisting}

В этом примере в переменную-указатель ptr записывается адрес начала массива a, т. е. адрес первого элемента массива.
Также справедливы следующие соотношения: например, имеется массив a[N], тогда истинными будут следующие сравнения:
\begin{lstlisting}
a==&a[0];
*a==a[0].
\end{lstlisting}

Указатели можно увеличивать или уменьшать на целое число:
\begin{lstlisting}
ptr=a+1;
\end{lstlisting}

Теперь указатель ptr будет указывать на второй элемент массива a, что эквивалентно \&a[1].

При увеличении указателя на единицу адрес, который он представляет, увеличивается на размер объекта связанного с ним типа, например:
\begin{lstlisting}
int a[25];
int *ptr=a;
ptr+=3;
\end{lstlisting}

Первоначально указатель ptr указывал на начало массива a. После прибавления к переменной ptr числа 3 значение указателя увеличилось на3*sizeof(int), а указатель ptr теперь будет указывать на четвертый элемент массива a. Указатель можно индексировать точно так же, как и массив. На самом деле компилятор преобразует индексацию в арифметику указателей, например, ptr[3]=10 представляется как *(ptr+3)=10.

К указателям типа void арифметические операции применять нельзя, так как им не ставится в соответствие размер области памяти.

Таким образом, в языке C для доступа к элементам массива существует два различных способа. Первый способ связан с использованием обычных индексных выражений в квадратных скобках, например, a[7]=3 или a[i+2]=5. При таком способе доступа записываются два выражения, причем второе выражение заключается в квадратные скобки. Первое из этих выражений должно быть указателем, а второе – выражением целого типа. Указатель, используемый в индексном выражении, не обязательно должен быть константой, указывающей на какой-либо массив, это может быть и переменная-указатель. В частности, после выполнения присваивания ptr=a доступ к седьмому элементу массива можно получить как с помощью константы-указателя a в форме a[7], так и переменной-указателя ptr в форме ptr[7].

Второй способ доступа к элементам массива связан с использованием адресных выражений и операции косвенной адресации в форме *(a+3)=10 или *(a+i+2)=5.

При реализации на компьютере первый способ приводится ко второму, т. е. индексное выражение приводится к адресному. Для приведенных примеров обращение к элементу массива a[3] преобразуется в *(a+3).

Для доступа к начальному элементу массива, т. е. к элементу с нулевым индексом, можно использовать просто значение указателя a или ptr, поэтому любое из присваиваний
\begin{lstlisting}
*a=2;
a[0]=2;
*(a+0)=2;
*ptr=2;
ptr[0]=2;
*(ptr+0)=2;
\end{lstlisting}
присваивает начальному элементу массива значение 2.

\subsubsection{Многомерные массивы}
Многомерные массивы в языке C – это массивы массивов, т. е. массивы, элементами которых, в свою очередь, являются массивы. При объявлении таких массивов в памяти компьютера создается несколько различных объектов. Например, при выполнении объявления двумерного массива int a2[4][3] в программе создается указатель a2, который определяет в памяти местоположение первого элемента массива и, кроме того, является указателем на массив из четырех указателей. Каждый из этих четырех указателей содержит адрес одномерного массива, представляющего собой строку двумерного массива и состоящего из трех элементов типа int, и позволяет обратиться к соответствующей строке массива.

Таким образом, объявление a2[4][3] порождает в программе три разных объекта: указатель с идентификатором a2, безымянный массив из четырех указателей и безымянный массив из двенадцати чисел типа int. Для доступа к безымянным массивам используются адресные выражения с указателем a2. Доступ к элементам массива указателей осуществляется с указанием одного индексного выражения в форме a2[2]или *(a2+2). Для доступа к элементам двумерного массива чисел типа int должны быть использованы два индексных выражения в формеa2[1][2] или эквивалентных ей *(*(a2+1)+2) и (*(a2+1))[2]. Следует учитывать, что с точки зрения синтаксиса языка C указательa2 и указатели a2[0], a2[1], a2[2], a2[3] являются константами, и их значения нельзя изменять во время выполнения программы.

Размещение трехмерного массива происходит аналогично. Так, например, объявление float a3[3][4][5] порождает в программе, кроме самого трехмерного массива из 60 чисел типа float, массив из четырех указателей на тип float, массив из трех указателей на массив указателей на float и указатель на массив массивов указателей на float.

При размещении элементов многомерных массивов они располагаются в памяти подряд по строкам, т. е. быстрее всего изменяется последний индекс, а медленнее – первый. Такой порядок дает возможность обращаться к любому элементу многомерного массива, используя адрес его начального элемента и только одно индексное выражение.

Например, обращение к элементу a2[1][2] можно осуществить при помощи указателя ptr2, объявленного в форме int *ptr2=a2[0], как обращение ptr2[1?3+2] (здесь 1 и 2 – это индексы используемого элемента, а 3 – число элементов в строке) или как ptr2[5]. Заметим, что внешне похожее обращение a2[6] выполнить невозможно, так как указателя с индексом 6 не существует.

Для обращения к элементу a3[2][3][4] из трехмерного массива тоже можно использовать указатель, описанный как float *ptr3=a3[0][0], с одним индексным выражением в форме ptr3[2?20+3?5+4] или ptr3[59].

\subsubsection{Массивы и функции}
Использование указателей в качестве формальных параметров функции дает возможность передавать из вызывающей подпрограммы в функцию не само значение или массив значений, а адрес переменной (адреса фактических аргументов).

В частности, при работе с массивами не требуется использовать в качестве формальных аргументов массив. Это может быть указатель того же типа, что и тип элемента массива. В подпрограмму в этом случае можно передавать адрес начального элемента массива.

Пример программы
Задан одномерный массив из N элементов. Найти значение максимального элемента массива.
Поиск максимума оформить в виде функции max:
\begin{lstlisting}
# include <stdio.h>
# define N 3
int max(int k,int* b)
{
  int i,m1;
  m1=*b; 
  for(i=1; i<k; ++i)
  {
    b++;
    if (m1<*b)m1=*b; 
  }
  return(m1);
}
int main()
{
  static int A[N]={1,7,3};
  printf("\n max=%d",max(N,&A[0]));
  return 0;
}
\end{lstlisting}

Рассмотрим теперь пример программы с двумерным массивом. Дан двумерный массив, состоящий из трех строк и четырех столбцов. Найти максимальный элемент каждой строки, используя ранее созданную функцию max:

\begin{lstlisting}
int main()
{
  static int B[3][4]={3,5,1,6,8,3,7,3,2,6,9,3};
  printf("\n 1 row: max=%d",max(4,B[0]));
  printf("\n 2 row: max=%d",max(4,B[1]));
  printf("\n 3 row: max=%d",max(4,B[2]));
  return 0;
}
\end{lstlisting}

При вызове функции max использовались указатели на начало строк массива, каждая строка массива содержит 4 значения.
Результат работы программы:
\textit{
1 row: max=6
2 row: max=8
3 row: max=9.
}

Рассмотрим передачу двумерного массива в функцию. В качестве параметров функции передается адрес начала массива и количество элементов в строке массива, соответствующее объявлению массива в вызывающей функции (int A2[][N]), а также два целых числа m и n, имеющие смысл фактического количества строк и столбцов, используемых в массиве. Они не должны превышать значения количества строк и столбцов соответственно, заданные при объявлении массива.

В примере используются следующие функции: ввод двумерного массива input\_mas(), вывод на экран двумерного массива в виде таблицы output\_mas(), вычисление суммы элементов массива, имеющих одинаковые номера столбцов и строк int diagonal():
\begin{lstlisting}
#include <stdio.h>
#define N 4
#define M 3
void input_mas(int A2[][N],int m,int n);
void output_mas(int A2[][N],int m,int n);
int diagonal(int A2[][N],int m,int n);
void main()
{
  int mas[M][N];
  input_mas(mas,M,N);
  output_mas(mas,M,N);
  printf("Summa elementov diagonali = %d\n", diagonal(mas,M,N));
  return 0;
}
void input_mas(int A2[][N],int m,int n)
{
  int i,j;
  printf("Wwedite chisla->");
  for(i=0;i<M;i++)
    for(j=0;j<N;j++)
      scanf("%d",&A2[i][j]);
}
void output_mas(int A2[][N],int m,int n)
{
  int i,j;
  printf("Wwedeno:\n");
  for(i=0;i<M;i++)
  {
    for(j=0;j<N;j++) printf("%7d",A2[i][j]);
    printf("\n");
  }
}
int diagonal(int A2[][N],int m,int n)
{
  int i,j,summa=0;
  for(i=0;i<M;i++)
    for(j=0;j<N;j++)
      if (i==j) summa+=A2[i][j];
  return summa;
}
\end{lstlisting}

\subsection{Строки}
В языке C, в отличие от языка Pascal, отсутствует специальный строковый тип данных. Строка представляет собой последовательность (одномерный массив) из одного или более символов, последним из которых является нулевой символ таблицы ASCII '$\backslash$0'. Это единственный вид строки, определенный в C.

Язык C поддерживает строковые константы, называемые строковыми литералами. Строковый литерал – это любая последовательность символов, заключенная в двойные кавычки (""). В конце литерала компилятор автоматически добавляет нулевой символ.

Не следует путать понятия строки и символа. Символьная константа заключается в одинарные кавычки, а строковая – в двойные.

Например, 'c' – символьная константа, а "c" – строковая константа.

Строковая переменная может быть сформирована как одномерный массив типа char либо с помощью указателя на переменную типа char. Количество символов в массиве (объем выделяемой памяти) должно быть не меньше, чем количество символов в строке плюс один символ для хранения символа нуль.

Представление символьной строки при помощи одномерного массива
Синтаксис объявления имеет вид:
\begin{lstlisting}
char ID [N];
\end{lstlisting}
где ID – идентификатор массива, N – длина массива, при этом в памяти для хранения строки выделяется N байт.

Например, для переменной 
\begin{lstlisting}
char ST[10] 
\end{lstlisting}
в памяти выделяется 10 байт, что дает возможность сформировать строку из 9 символов. Для таких строк действуют все правила представления и обработки массивов.

Идентификатор массива – константа типа указатель, значение которой равно адресу первого элемента массива.

Инициализация возможна двумя способами:
\begin{itemize}
\itemпосимвольная инициализация 
\begin{lstlisting}
char st[10]={'y','e','s','\0'\}; 
\end{lstlisting}
при этом оставшиеся 6 позиций не будут заполнены;
\itemциализация на основе строковой константы 
\begin{lstlisting}
char st [10]="Yes"; 
\end{lstlisting}
при этом в выделенную для строки память будут помещены 3 символа и добавлен четвертый – символ '$\backslash$0'
\end{itemize}
Инициализация и объявление возможны без указания длины массива 
\begin{lstlisting}
char st[]={'y','e','s','\0'};
\end{lstlisting}
в этом случае будет создан массив из четырех элементов.

\subsection{Функции работы со строками}
Язык C определяет строки как особый вид массивов, позволяя осуществлять их ввод/вывод как единого целого. Встроенные средства обработки строк в языке C отсутствуют, но строки настолько широко используются в программировании, что большинство компиляторов имеет специальные функции для работы со строками. Эти функции входят в состав заголовочного файла <string.h>.

Отметим ряд основных функций.

Определение длины строки:
strlen (S1) – длина строки S1, исключая нулевой символ.

Копирование строк:
strcpy (S1,S2), где S1 – указатель, S2 – указатель или константа.
Строка S2 копируется посимвольно в строку S1. Необходимо иметь в виду, что размер строк компилятором не сравнивается, и это возлагается на программиста.

Слияние строк (конкатенация):
strcat (S1,S2).
К концу строки S1 подсоединяется строка S2. Нулевой символ в конце строки S1 отбрасывается. Компилятор не следит, хватит ли в S1 места для S1 плюс S2.

Сравнение строк:
strcmp (S1,S2).
Сравниваются коды символов, находящихся на одинаковых позициях в строках S1 и S2, начиная с нулевой. В зависимости от компилятора результат работы функции следующий:
\begin{itemize}
\itemвозвращается нуль, если строки одинаковы, и значение отличное от нуля, если строки не совпадают;
\itemотрицательное число, если строка S1 «меньше» строки S2 с точки зрения алфавита (ASCII-кодов), и положительное, если «больше».
\end{itemize}

Данная функция используется для упорядочения по какому-либо критерию.

\subsection{Функции ввода/вывода printf() и scanf()}
Функция printf() позволяет выводить информацию на экран при программировании в консольном режиме. Данная функция определена в библиотеке stdio.h и имеет следующий синтаксис:

\begin{lstlisting}
int printf( const char *format [, argument]... );
\end{lstlisting}

Здесь первый аргумент *format определяет строку, которая выводится на экран и может содержать специальные управляющие символы для вывода переменных. Затем, следует список необязательных аргументов, которые поясняются ниже. Функуция возвращает либо число отображенных символов, либо отрицательное число в случае своей некорректной работы.

В самой простой реализации функция printf() просто выводит заданную строку на экран монитора:

\begin{lstlisting}
printf("Hello, world!");
\end{lstlisting}

Однако с ее помощью можно выводить переменные разного типа: начиная с числовых и заканчивая строковыми. Для выполнения этой операции используются специальные управляющие символы, которые называются спецификаторами и которые начинаются с символа \%. Следующий пример демонстрирует вывод целочисленной переменной num на экран монитора с помощью функции printf():

\begin{lstlisting}
int num;
num = 5;
printf("%d", num);
\end{lstlisting}

В первых двух строках данной программы задается переменная с именем num типа int. В третьей строке выполняется вывод переменной на экран. Работа функции printf() выглядит следующим образом. Сначала функция анализирует строку, которую необходимо вывести на экран. В данном случае это "\%d". Если в этой строке встречается спецификатор, то на его место записывается значение переменной, которая является вторым аргументом функции printf(). В результате, вместо исходной строки "\%d" на экране появится строка <<5>>, т.е. будет выведено число 5.

Следует отметить, что спецификатор "\%d" выводит только целочисленные типы переменных, например int. Для вывода других типов следует использовать другие спецификаторы. Ниже перечислены основные виды спецификаторов:

\%с – одиночный символ
\%d – десятичное целое число со знаком
\%f – число с плавающей точкой (десятичное представление)
\%s – строка символов (для строковых переменных)
\%u – десятичное целое без знака
\%\% - печать знака процента

С помощью функции printf() можно выводить сразу несколько переменных. Для этого используется следующая конструкция:

\begin{lstlisting}
int num_i;
float num_f;
num_i = 5;
num_f = 10.5;
printf("num_i = %d, num_f = %f", num_i, num_f);
\end{lstlisting}

Результат выполнения программы будет выглядеть так:

\begin{lstlisting}
num_i = 5, num_f = 10.5
\end{lstlisting}

Кроме спецификаторов в функции printf() используются управляющие символы, такие как перевод строки $\backslash$n, табуляции $\backslash$t и др. Например, если в ранее рассмотренном примере необходимо вывести значения переменных не в строчку, а в столбик, то необходимо переписать функцию printf() следующим образом:

\begin{lstlisting}
printf("num_i = %d,\n num_f = %f", num_i, num_f);
\end{lstlisting}

Аналогично используется и символ табуляции.

Для ввода информации с клавиатуры удобно использовать функцию scanf() библиотеки stdio.h, которая имеет следующий синтаксис:

\begin{lstlisting}
int scanf( const char *format [,argument]... );
\end{lstlisting}

Здесь, как и для функции printf(), переменная *format определяет форматную строку для определения типа вводимых данных и может содержать те же спецификаторы что и функция printf(). Затем, следует список необязательных аргументов. Работа функции scanf() демонстрируется на листинге 1.4.

\begin{lstlisting}
int main()
{
  int age;
  float weight;
  scanf("%d", &age);
  scanf("%f", &weigth);
  printf("age = %d, weight = %f", age, weigth);

  return 0;
}
\end{lstlisting}

Основным отличием применения функции scanf() от функции printf() является знак \& перед именем переменной, в которую записываются результаты ввода.

Функция scanf() может работать сразу с несколькими переменными. Предположим, что необходимо ввести два целых числа с клавиатуры. Формально для этого можно дважды вызвать функцию scanf(), однако лучше воспользоваться такой конструкцией:

\begin{lstlisting}
scanf(" %d, %d ", &n, &m);
\end{lstlisting}

Функция scanf() интерпретирует это так, как будто ожидает, что пользователь введет число, затем – запятую, а затем – второе число. Все происходит так, как будто требуется ввести два целых числа следующим образом:

88,221

или

88, 221

Функция scanf() возвращает число успешно считанных элементов. Если операции считывания не происходило, что бывает в том случае, когда вместо ожидаемого цифрового значения вводится какая-либо буква, то возвращаемое значение равно 0. 
 Помимо основных функций ввода/вывода, система буферизированного ввода/вывода содержит fprintf() и fscanf(). Данные функции ведут себя так же, как и printf() и scanf(), за тем исключением, что работают с дисковыми файлами. Они имеют следующие прототипы:

\subsection{Файловый ввод-вывод при помощи fprintf и fscanf}

\begin{lstlisting}
int fprintf(FILE *fp, const char *, ...);
int fscanf(FILE *fp, const char *, ...);
\end{lstlisting}

где fp - это указатель на файл, возвращаемый fopen(). Если не принимать во внимание, что они перенаправляют вывод в файл, то можно считать, что работа данных функций полностью идентична printf() и scanf() соответственно.

Файл открывается при помощи fopen, которая возвращает информацию потока ввода-вывода, прикрепленного к указанному файлу или другому устройству, с которого идет чтение (или в который идет запись). В случае неудачи функция возвращает нулевой указатель.

Схожая функция freopen библиотеки Си выполняет аналогичную операцию после первого закрытия любого открытого потока, связанного с ее параметрами.

Они определяются как

\begin{lstlisting}
FILE *fopen(const char *path, const char *mode);
FILE *freopen(const char *path, const char *mode, FILE *fp);
\end{lstlisting}

Функция fopen по сути представляет собой «обертку» более высокого уровня системного вызова open операционной системы Unix. Аналогично, fclose является оберткой системного вызова Unix close, а сама структура FILE языка Си зачастую обращается к соответствующему файловому дескриптору Unix. В POSIX-окружении функция fdopen может использоваться для инициализации структуры FILE файловым дескриптором. Тем не менее, файловые дескрипторы как исключительно Unix-концепция не представлены в стандарте языка Си.

Параметр mode (режим) для fopen и freopen должен быть строковый и начинаться с одной из следующих последовательностей \\
r 	rb 		открывает для чтения 	c начала \\
w 	wb 		открывает для записи (создает файл в случае его отсутствия). Удаляет содержимое и перезаписывает файл. 	c начала \\
a 	ab 		открывает для добавления (создает файл в случае его отсутствия) 	c конца \\
r+ 	rb+ 	r+b 	открывает для чтения и записи 	c начала \\
w+ 	wb+ 	w+b 	открывает для чтения и записи. Удаляет содержимое и перезаписывает файл. 	c начала \\
a+ 	ab+ 	a+b 	открывает для чтения и записи (добавляет в случае существования файла) 	c конца \\

Функция fclose принимает один аргумент: указатель на структуру FILE потока для закрытия.

\begin{lstlisting}
int fclose(FILE *fp);
\end{lstlisting}

Функция возвращает нуль в случае успеха и EOF в случае неудачи. При нормальном завершении программы функция вызывается автоматически для каждого открытого файла.

\subsection{Структуры (struct)}

Использование структур обусловлено необходимостью объединить несколько переменных.

Структуры - это, обычно, группа переменных описывающих какую-нибудь сущность.

Например, в игре танк можно описать группой переменных: количество снарядов, количество горючего и т.д. У нас в примере танк будет обладать следующими параметрами: координаты и количество топлива (от нуля до двадцати).

Зачем нам использовать какие-то структуры спросите Вы? Ведь переменные и так прекрасно хранятся.

Причин две: переменные в структурах хранятся в одном месте и можно создавать несколько структурных переменных и у всех у них будут одинаковые характеристики.

Объявления у структуры нет. Её нужно сразу определять. Тело структуры должно находиться до начала main.

\begin{lstlisting}
struct tank
{
  int x, y;
  int fuel;
};
\end{lstlisting}

Здесь мы видим структуру с именем tank. Перед именем стоит ключевое слово struct (от structure - структура). В теле структуры находятся объявления переменных отвечающих за координаты и кол-во топлива. Обратите внимание, что после закрывающей фигурной скобки стоит точка с запятой. Не забывайте о ней в своих структурах.

На самом деле мы только что создали новый тип данных. Такие типы данных - определяемые программистом, называются пользовательскими. К пользовательским типам данных, помимо структур, относятся и классы, но об этом в следующий раз.
Когда мы определяем структуру, под неё не выделяется память. Структура - это как шаблон. Теперь на основе структуры tank можно создать много структурных переменных. Все эти переменные будут иметь тип tank, и каждая структурная переменная будет иметь доступ к своим переменным x, y, fuel.

Сейчас нам нужно определить переменную типа tank. Или даже две:

\begin{lstlisting}
tank t34 = {0,0,20}; // x, y, fuel
tank pz4 = {8,7,20};
\end{lstlisting}

Обе переменные мы сразу проинициализировали (хотя этого можно было и не делать). Для этого использовали список значений в фигурных скобках. Значения в скобках присваиваются переменным внутри структуры в том порядке, в котором они были объявлены при определении структуры: x, y, fuel.

Теперь у нас есть две переменные t34, pz4 типа tank. У каждой есть поля x, y, fuel. Подчёркиваю красным: каждая структурная переменная обладает своим набором переменных, которые были объявлены в структуре.

Чтобы получить доступ к полю, нужно воспользоваться операцией доступа (операцией точки). Например:

\begin{lstlisting}
pz4.fuel = 100;
\end{lstlisting}

Сначала мы указываем имя структурной переменной, затем ставим точку и в конце указываем имя поля структуры. Здесь мы присвоили значение полю fuel структурной переменной pz4, а затем вывели значение поля fuel на экран.

Напоследок, вот как получить доступ ко всем полям двух структурных переменных:

\begin{lstlisting}
t34.x;
t34.y;
t34.fuel;
pz4.x;
pz4.y;
pz4.fuel;
\end{lstlisting}

\subsection{Перечисления (enum)}

Перечисления - это ещё один пользовательский тип данных. Перечисления используют для описания какого-то небольшого множества значений.

С помощью перечислений можно задать дни недели, месяцы, ну или что-нибудь подобное. В качестве примера рассмотрим стороны света. Стороны света на экране расположены так: вверху - север (north), справа - восток (east), слева - запад (west) и внизу - юг (south).

\begin{lstlisting}
enum cardinal_dirs { north, west, east, south };
\end{lstlisting}

Здесь мы определили перечисление cardinal\_dirs. В начале строки стоит ключевое слово enum (enumeration - перечисление). Затем указывается имя перечисления, после которого, через пробел, в фигурных скобках задаются значения, которые смогут принимать переменные типа cardinal\_dirs. После фигурных скобок ставится точка с запятой.

После того, как определено перечисление, можно создавать переменные нового типа:

\begin{lstlisting}
cardinal_dirs ch = north;
\end{lstlisting}

Обратите внимание, что переменные типа cardinal\_dirs могут принимать только четыре значения: north, south, east, west. Кроме того, элементы в перечислении нумеруются от нуля. Т.е. north = 0, east = 1, south = 2, west = 3. Вместо перечисления мы могли бы создать четыре константы:

\begin{lstlisting}
const int north = 0;
const int east = 1;
const int south = 2;
const int west = 3;
\end{lstlisting}

Результат одинаковый.

Как видите использовать перечисления довольно легко. Но стороны света у нас закодированы клавишами стрелочек, поэтому нам нужно инициализировать элементы перечисления числами. Как я уже писал, отсчёт ведётся с нуля. Но, к счастью, это можно изменить. Если мы переопределим какой-либо элемент значением 75, то следующий, получит значение 76. Нам нужно переопределить все четыре значения.

\begin{lstlisting}
enum cardinal_dirs { north = 72, west = 75, east = 77, south = 80 };
\end{lstlisting}

Про структуры и перечисления всё. Есть ещё некоторые важные моменты по структурам, но нам они пока не нужны.

Ещё раз повторюсь, точно такого же результата можно было бы добиться с помощью четырёх констант.

\subsection{Объединения (union)}
 Объединение - это тоже пользовательский тип данных, который очень похож на структуру. Только тут все данные объединения занимают одну и туже область в памяти. Т.е. на каком-то этапе вам нужен один тип данных, на другом - другой. В общем, объединение экономит вашу память от ненужных на данном этапе переменных.

Так как объединение хранит и использует всегда одно поле их множества на выбор, то возникает вопрос о выделении памяти под это поле. Тут принцип понятный - выбирается наибольший из типов данных.

Все то же самое, как и объявление структуры, только вместо специального слова struct используется union. Вот пример:
\begin{lstlisting}
union chislo {
   int a;
   float b;
};
\end{lstlisting}

Разрешенные операции:
\begin{itemize}
\itemможно присваивать объединения друг другу
\itemадрес брать так же ни кто не запрещал
\itemк элементам можно получить доступ, так же как и в структурах, т.е. через (.) или (->)
\end{itemize}

Объединение ( union ) можно инициализировать только один значением, причем оно должно соответствовать первому элементу этого объединения. В нашем случае:
\begin{lstlisting}
union chislo A = {34 }; // пойдет
union chislo B = {34.56 }; // нельзя
\end{lstlisting}
У нас первым элементом расположено поле int, поэтому при инициализации так же должно быть поле int.

Объединения используются когда необходимо получить доступ к одним и тем же данным разными способами.

\subsection{Указатели на функции}
Указатели на функции— очень мощное средство языка С. Хотя нельзя не отметить, что это весьма трудный для понимания термин. Функция располагается в памяти по определенному адресу, который можно присвоить указателю в качестве его значения. Адресом функции является ее точка входа. Именно этот адрес используется при вызове функции. Так как указатель хранит адрес функции, то она может быть вызвана с помощью этого указателя. Он позволяет также передавать ее другим функциям в качестве аргумента.

В программе на С адресом функции служит ее имя без скобок и аргументов (это похоже на адрес массива, который равен имени массива без индексов). Рассмотрим следующую программу, в которой сравниваются две строки, введенные пользователем. Обратите внимание на объявление функции check() и указатель p внутри main(). Указатель p, как вы увидите, является указателем на функцию.
\begin{lstlisting}
#include <stdio.h>
#include <string.h>

void check(char *a, char *b,
           int (*cmp)(const char *, const char *));

int main(void)
{
  char s1[80], s2[80];
  int (*p)(const char *, const char *);
               /* указатель на функцию */

  p = strcmp;
  /* присваивает адрес функции strcmp указателю p */

  printf("Введите две строки.\n");
  gets(s1);
  gets(s2);

  check(s1, s2, p); /* Передает адрес функции strcmp
                       посредством указателя p */

  return 0;
}

void check(char *a, char *b,
           int (*cmp)(const char *, const char *))
{
  printf("Проверка на совпадение.\n");
  if(!(*cmp)(a, b)) printf("Равны");
  else printf("Не равны");
}
\end{lstlisting}

Проанализируем эту программу подробно. В первую очередь рассмотрим объявление указателя pв main():
\begin{lstlisting}
int (*p)(const char *, const char *);
\end{lstlisting}

Это объявление сообщает компилятору, что p — это указатель на функцию, имеющую два параметра типа const char * и возвращающую значение типа int. Скобки вокруг p необходимы для правильной интерпретации объявления компилятором. Подобная форма объявления используется также для указателей на любые другие функции, нужно лишь внести изменения в зависимости от возвращаемого типа и параметров функции.

Теперь рассмотрим функцию check(). В ней объявлены три параметра: два указателя на символьный тип (a и b) и указатель на функцию cmp. Обратите внимание на то, что указатель функции cmp объявлен в том же формате, что и p. Поэтому в cmp можно хранить значение указателя на функцию, имеющую два параметра типа const char * и возвращающую значение int. Как и в объявлении p, круглые скобки вокруг *cmp необходимы для правильной интерпретации этого объявления компилятором.

Вначале в программе указателю p присваивается адрес стандартной библиотечной функцииstrcmp(), которая сравнивает строки. Потом программа просит пользователя ввести две строки и передает указатели на них функции check(), которая их сравнивает. Внутри check() выражение
\begin{lstlisting}
(*cmp)(a, b)
\end{lstlisting}

вызывает функцию strcmp(), на которую указывает cmp, с аргументами a и b. Скобки вокруг *cmpобязательны. Существует и другой, более простой, способ вызова функции с помощью указателя:
\begin{lstlisting}
cmp(a, b);
\end{lstlisting}

Однако первый способ используется чаще (и мы рекомендуем использовать именно его), потому что при втором способе вызова указатель cmp очень похож на имя функции, что может сбить с толку читающего программу. В то же время у первого способа записи есть свои преимущества, например, хорошо видно, что функция вызывается с помощью указателя на функцию, а не имени функции. Следует отметить, что первоначально в С был определен именно первый способ вызова.

Вызов функции check() можно записать, используя непосредственно имя strcmp():
\begin{lstlisting}
check(s1, s2, strcmp);
\end{lstlisting}

В этом случае вводить в программу дополнительный указатель p нет необходимости.

У читателя может возникнуть вопрос: какая польза от вызова функции с помощью указателя на функцию? Ведь в данном случае никаких преимуществ не достигнуто, этим мы только усложнили программу. Тем не менее, во многих случаях оказывается более выгодным передать имя функции как параметр или даже создать массив функций. Например, в программе интерпретатора синтаксический анализатор (программа, анализирующая выражения) часто вызывает различные вспомогательные функции, такие как вычисление математических функций, процедуры ввода-вывода и т.п. В таких случаях чаще всего создают список функций и вызывают их с помощью индексов.
Альтернативный подход — использование оператора switch с длинным списком меток case — делает программу более громоздкой и подверженной ошибкам.

\chapter{Принципы построения программно-алгоритмического обеспечения вычислительных средств АИУС}

\section{Реализация прикладных программ в АИУС}
\subsection{Пример цифровых фильтров}
Уравнение цифрового фильтра:
\begin{equation}
y(k) = \frac{1}{a_0}\left(\sum\limits_{m=0}^{k}{b_m\cdot x(k-m)} -
\sum\limits_{m=1}^{k}{a_m\cdot y(k-m)}\right)
\end{equation}

где $y(k)$ - отсчеты на выходе фильтра, $x(k)$ - входные отсчеты, $b_m$ и $a_m$ - коэффициенты числителя и знаменателя передаточной характеристики фильтра соответственно. 
Также мы говорили о том, что если все коэффициенты $a_m$ кроме $a_0$ равны нулю, то такой фильтр называется КИХ-фильтром, а если хотя бы один коэффициент помимо $a_0$ отличен от нуля, то такой фильтр называется БИХ-фильтр.

Разностное уравнение КИХ фильтра не содержит рекурсивной части:
\begin{equation}
y(k) = \frac{1}{a_0}\left(\sum\limits_{m=0}^{k}{b_m\cdot x(k-m)}\right)
\end{equation}

В качестве примера реализуем КИХ-фильтр, который обрабатывает массив из 
1000 входных значений, а количество коэффициентов -- 100 штук.

\subsection{Альфа-бета фильтр}
Алгоритм рекурсивного типа.
\begin{equation}
\begin{array}{l}
x^s(k) = x^p(k) + \alpha[x^m(k)-x^p(k)] \\
\dot{x}^s(k) = \dot{x}^p(k) + \frac{\beta}{T}[x^m(k) - x^p(k)] \\
\dot{x}^p(k+1) = \dot{x}^s \\
x^p(k+1) = x^s(k) + \dot{x}^s(k)T
\end{array}
\end{equation}

где $x^s(k)$ -- отфильтрованная координата на k-ом кванте времени, $x^p(k)$ -- предсказанная, $x^m(k)$ -- измеренная. С точками -- соответствующие скорости.

В качестве примера реализуем <<$\alpha-\beta$>>-фильтр, который обрабатывает массив из 
1000 входных значений.

\section{Автоматное программирование}
%Вставить потом

\addcontentsline{toc}{chapter}{Литература}
\begin{thebibliography}{99}
\bibitem{lee}Embedded Systems. Lee, Edward A. 2002. Vol. 56, London : Academic Press, 2002.
\bibitem{heath}Heath, Steve. 2003. Embedded Systems Design. s.l. : Newness, 2003. 
\end{thebibliography}

\end{document} % конец документа
>>>>>>> 0e53e9a... Материалы к курсу, мелкие правки
